package com.tsfyun.scm.service.impl.logistics;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.StrUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.Lists;
import com.tsfyun.common.base.config.ValidationUtil;
import com.tsfyun.common.base.enums.DistributedLockEnum;
import com.tsfyun.common.base.exception.ServiceException;
import com.tsfyun.common.base.util.TsfPreconditions;
import com.tsfyun.common.base.util.TypeUtils;
import com.tsfyun.common.base.validator.ValidatorUtils;
import com.tsfyun.scm.dto.logistics.OverseasDeliveryNoteBindOrderMemberDTO;
import com.tsfyun.scm.dto.logistics.OverseasDeliveryNoteBindQTO;
import com.tsfyun.scm.entity.logistics.OverseasDeliveryNote;
import com.tsfyun.scm.entity.logistics.OverseasDeliveryNoteMember;
import com.tsfyun.scm.mapper.logistics.OverseasDeliveryNoteMemberMapper;
import com.tsfyun.scm.service.logistics.IOverseasDeliveryNoteMemberService;
import com.tsfyun.common.base.extension.ServiceImpl;
import com.tsfyun.scm.service.logistics.IOverseasDeliveryNoteService;
import com.tsfyun.scm.util.MaterielUtil;
import com.tsfyun.scm.util.TsfWeekendSqls;
import com.tsfyun.scm.vo.logistics.OverseasDeliveryNoteMemberStockVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 *
 * @since 2021-11-09
 */
@Slf4j
@Service
public class OverseasDeliveryNoteMemberServiceImpl extends ServiceImpl<OverseasDeliveryNoteMember> implements IOverseasDeliveryNoteMemberService {

    @Autowired
    private OverseasDeliveryNoteMemberMapper overseasDeliveryNoteMemberMapper;
    @Autowired
    private IOverseasDeliveryNoteService overseasDeliveryNoteService;
    @Resource
    private Snowflake snowflake;
    @Resource
    private RedisLockRegistry redisLockRegistry;

    @Override
    public List<OverseasDeliveryNoteMember> findByOverseasDeliveryNoteId(Long overseasDeliveryNoteId) {
        return overseasDeliveryNoteMemberMapper.selectByExample(Example.builder(OverseasDeliveryNoteMember.class).where(TsfWeekendSqls.<OverseasDeliveryNoteMember>custom()
                .andEqualTo(false,OverseasDeliveryNoteMember::getDeliveryNoteId,overseasDeliveryNoteId)).build());
    }

    @Override
    public PageInfo<OverseasDeliveryNoteMemberStockVO> pageList(OverseasDeliveryNoteBindQTO qto) {
        PageHelper.startPage(qto.getPage(),qto.getLimit());
        List<OverseasDeliveryNoteMemberStockVO> dataList = overseasDeliveryNoteMemberMapper.queryStockOrderMembers(qto);
        return new PageInfo<>(dataList);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void bind(OverseasDeliveryNoteBindOrderMemberDTO dto) {
        OverseasDeliveryNote overseasDeliveryNote = overseasDeliveryNoteService.getById(dto.getOverseasDeliveryNoteId());
        Optional.ofNullable(overseasDeliveryNote).orElseThrow(()->new ServiceException("境外派送单数据不存在"));
        List<OverseasDeliveryNoteBindOrderMemberDTO.BindOrderMemberDTO> bindMembers =  dto.getMembers();
        List<OverseasDeliveryNoteMember> members = Lists.newArrayListWithExpectedSize(bindMembers.size());
        for(OverseasDeliveryNoteBindOrderMemberDTO.BindOrderMemberDTO bindMember : bindMembers) {
            //判断派送数量是否大于可派送数量
            OverseasDeliveryNoteBindQTO overseasDeliveryNoteBindQTO = new OverseasDeliveryNoteBindQTO();
            overseasDeliveryNoteBindQTO.setId(bindMember.getId());
            List<OverseasDeliveryNoteMemberStockVO> stockBindList = overseasDeliveryNoteMemberMapper.queryStockOrderMembers(overseasDeliveryNoteBindQTO);
            TsfPreconditions.checkArgument(CollUtil.isNotEmpty(stockBindList),new ServiceException("未找到订单明细数据，请重新查询勾选"));

            OverseasDeliveryNoteMemberStockVO overseasDeliveryNoteMemberStockVO = stockBindList.get(0);

            BigDecimal stockQuantity = overseasDeliveryNoteMemberStockVO.getQuantity().subtract(overseasDeliveryNoteMemberStockVO.getOutQuantity()).setScale(2, BigDecimal.ROUND_HALF_UP);
            ValidatorUtils.isTrue(stockQuantity.compareTo(bindMember.getQuantity()) >= 0,()->new ServiceException(StrUtil.format("订单：{}，型号：{} 可派送数量不足，请输入合适的派送数量",overseasDeliveryNoteMemberStockVO.getExpOrderNo(),overseasDeliveryNoteMemberStockVO.getModel())));

            Integer stockCartonNum = TypeUtils.castToInt(overseasDeliveryNoteMemberStockVO.getCartonNum(),0) - TypeUtils.castToInt(overseasDeliveryNoteMemberStockVO.getOutCartonNum(),0);
            ValidatorUtils.isTrue(stockCartonNum - bindMember.getCartonNum() >= 0,()->new ServiceException(StrUtil.format("订单：{}，型号：{} 可派送件数不足，请输入合适的派送件数",overseasDeliveryNoteMemberStockVO.getExpOrderNo(),overseasDeliveryNoteMemberStockVO.getModel())));

            String lockKey = DistributedLockEnum.EXP_DELIVERY_NOTE_BIND_ORDER_MEMBER.getCode() + ":" + overseasDeliveryNoteMemberStockVO.getId();
            Lock lock = redisLockRegistry.obtain(lockKey);
            boolean isLock;
            try {
                isLock = lock.tryLock(3, TimeUnit.SECONDS);
                ValidatorUtils.isTrue(isLock,()->{
                    log.error(StrUtil.format("未获取到锁，订单明细id：【%s】", overseasDeliveryNoteMemberStockVO.getId()));
                    return new ServiceException(StrUtil.format("订单：{}，型号：{}已被他人操作派送订单明细，本次操作失败，请稍后操作",overseasDeliveryNoteMemberStockVO.getExpOrderNo(),overseasDeliveryNoteMemberStockVO.getModel()));
                });
                //新增或修改派送数量
                OverseasDeliveryNoteMember overseasDeliveryNoteMember = overseasDeliveryNoteMemberMapper.selectOneByExample((Example.builder(OverseasDeliveryNoteMember.class).where(TsfWeekendSqls.<OverseasDeliveryNoteMember>custom()
                        .andEqualTo(false,OverseasDeliveryNoteMember::getDeliveryNoteId,overseasDeliveryNote.getId())
                        .andEqualTo(false,OverseasDeliveryNoteMember::getOrderMemberId,bindMember.getId())).build()));

                if(Objects.isNull(overseasDeliveryNoteMember)) {
                    overseasDeliveryNoteMember = new OverseasDeliveryNoteMember();
                    overseasDeliveryNoteMember.setId(snowflake.nextId());
                    overseasDeliveryNoteMember.setDeliveryNoteId(overseasDeliveryNote.getId());
                    overseasDeliveryNoteMember.setOrderMemberId(overseasDeliveryNoteMemberStockVO.getId());

                    overseasDeliveryNoteMember.setBrand(overseasDeliveryNoteMemberStockVO.getBrand());
                    overseasDeliveryNoteMember.setName(overseasDeliveryNoteMemberStockVO.getName());
                    overseasDeliveryNoteMember.setModel(overseasDeliveryNoteMemberStockVO.getModel());

                    overseasDeliveryNoteMember.setCartonNo(overseasDeliveryNoteMemberStockVO.getCartonNo());
                    overseasDeliveryNoteMember.setCartonNum(bindMember.getCartonNum());

                    overseasDeliveryNoteMember.setExpOrderNo(overseasDeliveryNoteMemberStockVO.getExpOrderNo());

                    //净重和毛重按照数量计算
                    BigDecimal outGrossWeight = MaterielUtil.calWeight(overseasDeliveryNoteMemberStockVO.getQuantity(),bindMember.getQuantity(),
                            overseasDeliveryNoteMemberStockVO.getGrossWeight(),overseasDeliveryNoteMemberStockVO.getOutQuantity(),
                            overseasDeliveryNoteMemberStockVO.getOutGrossWeight());

                    overseasDeliveryNoteMember.setGrossWeight(outGrossWeight);

                    BigDecimal outNetWeight = MaterielUtil.calWeight(overseasDeliveryNoteMemberStockVO.getQuantity(),bindMember.getQuantity(),
                            overseasDeliveryNoteMemberStockVO.getNetWeight(),overseasDeliveryNoteMemberStockVO.getOutQuantity(),
                            overseasDeliveryNoteMemberStockVO.getOutNetWeight());

                    overseasDeliveryNoteMember.setNetWeight(outNetWeight);

                    overseasDeliveryNoteMember.setSpec(overseasDeliveryNoteMemberStockVO.getSpec());
                    overseasDeliveryNoteMember.setTotalPrice(overseasDeliveryNoteMemberStockVO.getTotalPrice());
                    overseasDeliveryNoteMember.setUnitPrice(overseasDeliveryNoteMemberStockVO.getUnitPrice());
                    overseasDeliveryNoteMember.setUnitCode(overseasDeliveryNoteMemberStockVO.getUnitCode());
                    overseasDeliveryNoteMember.setUnitName(overseasDeliveryNoteMemberStockVO.getUnitName());
                    overseasDeliveryNoteMember.setPalletNumber(0);
                    overseasDeliveryNoteMember.setQuantity(bindMember.getQuantity());
                    overseasDeliveryNoteMember.setTotalPrice(overseasDeliveryNoteMemberStockVO.getUnitPrice().multiply(bindMember.getQuantity()).setScale(2,BigDecimal.ROUND_HALF_UP));

                    members.add(overseasDeliveryNoteMember);
                } else {
                    BigDecimal oldQuantity = overseasDeliveryNoteMember.getQuantity();
                    BigDecimal oldGrossWeight = overseasDeliveryNoteMember.getGrossWeight();
                    BigDecimal oldNetWeight = overseasDeliveryNoteMember.getNetWeight();
                    //更新件数
                    overseasDeliveryNoteMember.setCartonNum(TypeUtils.castToInt(overseasDeliveryNoteMember.getCartonNum(),0) + TypeUtils.castToInt(bindMember.getCartonNum(),0));
                    //更新派送数量
                    overseasDeliveryNoteMember.setQuantity(oldQuantity.add(bindMember.getQuantity()).setScale(2,BigDecimal.ROUND_HALF_UP));
                    //计算总价
                    overseasDeliveryNoteMember.setTotalPrice(overseasDeliveryNoteMember.getUnitPrice().multiply(overseasDeliveryNoteMember.getQuantity()).setScale(2,BigDecimal.ROUND_HALF_UP));

                    //净重和毛重按照数量计算
                    BigDecimal outGrossWeight = MaterielUtil.calWeight(overseasDeliveryNoteMemberStockVO.getQuantity(),bindMember.getQuantity(),
                            overseasDeliveryNoteMemberStockVO.getGrossWeight(),overseasDeliveryNoteMemberStockVO.getOutQuantity(),
                            overseasDeliveryNoteMemberStockVO.getOutGrossWeight());

                    overseasDeliveryNoteMember.setGrossWeight(oldGrossWeight.add(outGrossWeight).setScale(4,BigDecimal.ROUND_HALF_UP));

                    BigDecimal outNetWeight = MaterielUtil.calWeight(overseasDeliveryNoteMemberStockVO.getQuantity(),bindMember.getQuantity(),
                            overseasDeliveryNoteMemberStockVO.getNetWeight(),overseasDeliveryNoteMemberStockVO.getOutQuantity(),
                            overseasDeliveryNoteMemberStockVO.getOutNetWeight());

                    overseasDeliveryNoteMember.setNetWeight(oldNetWeight.add(outNetWeight).setScale(4,BigDecimal.ROUND_HALF_UP));

                    //此处使用乐观锁，防止更新异常
                    int updateCnt = overseasDeliveryNoteMemberMapper.updateQuantity(overseasDeliveryNoteMember.getId(),oldQuantity,overseasDeliveryNoteMember.getQuantity(),overseasDeliveryNoteMember.getTotalPrice(),overseasDeliveryNoteMember.getCartonNum(),
                            overseasDeliveryNoteMember.getGrossWeight(),overseasDeliveryNoteMember.getNetWeight());
                    ValidatorUtils.isTrue(updateCnt == 1,()->{
                        log.info(StrUtil.format("订单：{}，型号：{}，原派送数量：{}，本次增加派送数量：{}",overseasDeliveryNoteMemberStockVO.getExpOrderNo(),oldQuantity,bindMember.getQuantity()));
                        return new ServiceException(StrUtil.format("订单：{}，型号：{}已被他人操作派送订单明细，本次操作失败，请稍后操作",overseasDeliveryNoteMemberStockVO.getExpOrderNo(),overseasDeliveryNoteMemberStockVO.getModel()));
                    });
                }
            } catch (InterruptedException e) {
                log.error("出口境外派送单绑定订单明细获取锁异常",e);
                throw new ServiceException("服务拥挤，请稍后再试");
            } finally {
                //释放锁
                lock.unlock();
            }
        }
        super.savaBatch(members);
        List<OverseasDeliveryNoteMember> newMembers = overseasDeliveryNoteMemberMapper.selectByExample(Example.builder(OverseasDeliveryNoteMember.class).where(
                TsfWeekendSqls.<OverseasDeliveryNoteMember>custom().andEqualTo(false,OverseasDeliveryNoteMember::getDeliveryNoteId,overseasDeliveryNote.getId())
        ).select("expOrderNo").build());
        List<String> expOrderNoList = newMembers.stream().map(OverseasDeliveryNoteMember::getExpOrderNo).distinct().collect(Collectors.toList());
        //更新主单的订单号
        String expOrderNos = StringUtils.join(expOrderNoList.stream().distinct().collect(Collectors.toList()),",");
        overseasDeliveryNoteService.updateExpOrderNos(overseasDeliveryNote.getId(),expOrderNos);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteByOverseasDeliveryNoteId(Long overseasDeliveryNoteId) {
        overseasDeliveryNoteMemberMapper.deleteByExample(Example.builder(OverseasDeliveryNoteMember.class).where(
                TsfWeekendSqls.<OverseasDeliveryNoteMember>custom().andEqualTo(false,OverseasDeliveryNoteMember::getDeliveryNoteId,overseasDeliveryNoteId)
        ).build());
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteByIds(Long overseasDeliveryId,List<Long> ids) {
        if(CollUtil.isEmpty(ids)) {
            throw new ServiceException("请至少选择一条数据");
        }
        OverseasDeliveryNote overseasDeliveryNote = overseasDeliveryNoteService.getById(overseasDeliveryId);
        Optional.ofNullable(overseasDeliveryNote).orElseThrow(()->new ServiceException("境外派送单数据不存在"));
        overseasDeliveryNoteMemberMapper.deleteByExample(Example.builder(OverseasDeliveryNoteMember.class).where(
                TsfWeekendSqls.<OverseasDeliveryNoteMember>custom().andIn(false,OverseasDeliveryNoteMember::getId,ids)
                .andEqualTo(false,OverseasDeliveryNoteMember::getDeliveryNoteId,overseasDeliveryId)
        ).build());
        List<OverseasDeliveryNoteMember> members = overseasDeliveryNoteMemberMapper.selectByExample(Example.builder(OverseasDeliveryNoteMember.class).where(
                TsfWeekendSqls.<OverseasDeliveryNoteMember>custom().andEqualTo(false,OverseasDeliveryNoteMember::getDeliveryNoteId,overseasDeliveryId)
        ).select("expOrderNo").build());
        List<String> expOrderNoList = members.stream().map(OverseasDeliveryNoteMember::getExpOrderNo).distinct().collect(Collectors.toList());
        //更新主单的订单号
        String expOrderNos = StringUtils.join(expOrderNoList.stream().distinct().collect(Collectors.toList()),",");
        overseasDeliveryNoteService.updateExpOrderNos(overseasDeliveryNote.getId(),expOrderNos);
    }

}
